/* Copyright (C) 2008 Robin Debreuil -- Released under the BSD License */

using System;
using System.Collections.Generic;
using System.CodeDom.Compiler;
using System.IO;
using System.Diagnostics;
using System.Text;
using Microsoft.Xna.Framework.Graphics;


namespace SWF
{
	public class SwfCompilationUnit 
	{
		private SwfReader _Reader;

		private TagType _CurrentTag;
		private uint _CurrentTagLength;

		public string Name;
		public string FullPath;
		public SwfHeader Header;
		public List<ISwfTag> Tags = new List<ISwfTag>();
		public JPEGTables JpegTable;

		public bool IsValid;
		public List<byte[]> TimelineStream = new List<byte[]>();
		public Dictionary<uint, DefineFont2_3> Fonts = new Dictionary<uint, DefineFont2_3>();
		public StringBuilder Log;

		public SwfCompilationUnit(SwfReader reader) 
            : this(reader, "Nameless")
		{
		}
		public SwfCompilationUnit(SwfReader reader, string name)
		{
			Name = name;
			FullPath = Directory.GetCurrentDirectory() + "/" + name + ".swf";
			_Reader = reader;
			Log = new StringBuilder();
			
			IsValid = _ParseHeader();

			if (IsValid)
			{
				_ParseTags();
			}
			_Reader = null;
			_CurrentTagLength = 0;
		}

		private bool _ParseHeader()
		{
			Header = new SwfHeader(_Reader);
			return Header.IsSwf;
		}

		private void _ParseTags()
		{
			bool tagsRemain = true;

			while (tagsRemain)
			{
				/*
					RECORDHEADER (short)
					Field				Type	Comment
					TagCodeAndLength	UI16	Upper 10 bits: tag typeLower 6 bits: tag length
				 * 
					RECORDHEADER (long)
					Field				Type	Comment
					TagCodeAndLength	UI16	Tag type and length of 0x3F Packed together as in short header
					Length				SI32	Length of tag
				*/
				uint b = _Reader.GetUI16();
				_CurrentTag = (TagType)(b >> 6);
				_CurrentTagLength = b & 0x3F;
				if (_CurrentTagLength == 0x3F)
				{
					_CurrentTagLength = _Reader.GetUI32();
				}
				uint tagEnd = _Reader.Position + _CurrentTagLength;
                //Debug.WriteLine(r.Position + " type: " + ((uint)curTag).ToString("X2") + " -- " + Enum.GetName(typeof(TagType), curTag));
					
				switch (_CurrentTag)
				{
					case TagType.End:
						Tags.Add(new EndTag(_Reader));
						tagsRemain = false;
						break;

					case TagType.FileAttributes:
						Tags.Add(new FileAttributesTag(_Reader));
						break;

					case TagType.BackgroundColor:
						Tags.Add(new BackgroundColorTag(_Reader));
						break;

					case TagType.DefineShape:
						Tags.Add(new DefineShapeTag(_Reader));
						break;

					case TagType.DefineShape2:
						Tags.Add(new DefineShape2Tag(_Reader));
						break;

					case TagType.DefineShape3:
						Tags.Add(new DefineShape3Tag(_Reader));
						break;

					case TagType.DefineShape4:
						Tags.Add(new DefineShape4Tag(_Reader));
						break;

					case TagType.PlaceObject:
						Tags.Add(new PlaceObjectTag(_Reader, tagEnd));
						break;

					case TagType.PlaceObject2:
						Tags.Add(new PlaceObject2Tag(_Reader, this.Header.Version));
						break;

					case TagType.PlaceObject3:
						Tags.Add(new PlaceObject3Tag(_Reader));
						break;

					case TagType.RemoveObject:
						Tags.Add(new RemoveObjectTag(_Reader));
						break;

					case TagType.RemoveObject2:
						Tags.Add(new RemoveObject2Tag(_Reader));
						break;

					case TagType.ShowFrame:
						Tags.Add(new ShowFrame(_Reader));
						break;

					case TagType.FrameLabel:
						Tags.Add(new FrameLabelTag(_Reader));
						break;

					case TagType.DefineSprite:
						DefineSpriteTag sp = new DefineSpriteTag(_Reader, this.Header.Version);
						Tags.Add(sp);
						break;

					// Bitmaps

					case TagType.JPEGTables:
                        JpegTable = new JPEGTables(_Reader, _CurrentTagLength);
                        Tags.Add(JpegTable);
						break;

					case TagType.DefineBits:
						Tags.Add(new DefineBitsTag(_Reader, _CurrentTagLength, false, false));
					    break;

					case TagType.DefineBitsJPEG2:
						Tags.Add(new DefineBitsTag(_Reader, _CurrentTagLength, true, false));
						break;

					case TagType.DefineBitsJPEG3:
						Tags.Add(new DefineBitsTag(_Reader, _CurrentTagLength, true, true));
					    break;

					case TagType.DefineBitsLossless:
						Tags.Add(new DefineBitsLosslessTag(_Reader, _CurrentTagLength, false));
					    break;

					case TagType.DefineBitsLossless2:
						Tags.Add(new DefineBitsLosslessTag(_Reader, _CurrentTagLength, true));
					    break;

					// Sound

					case TagType.DefineSound:
						Tags.Add(new DefineSoundTag(_Reader, _CurrentTagLength));
						break;

					case TagType.StartSound:
						Tags.Add(new StartSoundTag(_Reader));
						break;

					case TagType.SoundStreamHead:
					case TagType.SoundStreamHead2:
					    Tags.Add(new SoundStreamHeadTag(_Reader));
					    break;

					case TagType.SoundStreamBlock:
						SoundStreamBlockTag ssb = new SoundStreamBlockTag(_Reader, _CurrentTagLength);
						TimelineStream.Add(ssb.SoundData);
						Tags.Add(ssb);
						break;

                    // text

					case TagType.DefineFontInfo:
						break;
					case TagType.DefineFontInfo2:
						break;
					case TagType.DefineFont:
						break;
					case TagType.DefineFont2:
						DefineFont2_3 df2 = new DefineFont2_3(_Reader, false);
						Tags.Add(df2);
						Fonts.Add(df2.FontId, df2);
						break;
					case TagType.DefineFont3:
						DefineFont2_3 df3 = new DefineFont2_3(_Reader, true);
						Tags.Add(df3);
						Fonts.Add(df3.FontId, df3);
						break;
					case TagType.DefineFontAlignZones:
						DefineFontAlignZonesTag dfaz = new DefineFontAlignZonesTag(_Reader, Fonts);
						Tags.Add(dfaz);
						break;
					case TagType.CSMTextSettings:
						CSMTextSettingsTag csm = new CSMTextSettingsTag(_Reader);
						Tags.Add(csm);
						break;
					case TagType.DefineText:
						DefineTextTag dt = new DefineTextTag(_Reader, false);
						Tags.Add(dt);
						break;
					case TagType.DefineText2:
						DefineTextTag dt2 = new DefineTextTag(_Reader, true);
						Tags.Add(dt2);
                        break;
                    case TagType.DefineEditText:
                        Tags.Add(new DefineEditTextTag(_Reader));
                        break;
                    case TagType.DefineFontName:
                        Tags.Add(new DefineFontName(_Reader));
                        break;

                    // buttons
                    case TagType.DefineButton:
                        Tags.Add(new DefineButton(_Reader));
                        break;
                    case TagType.DefineButton2:
                        Tags.Add(new DefineButton2(_Reader));
                        break;
                    case TagType.DefineButtonCxform:
                        Tags.Add(new DefineButtonCxform(_Reader));
                        break;
                    case TagType.DefineButtonSound:
                        Tags.Add(new DefineButtonSound(_Reader));
                        break;

                    // actions
					case TagType.ExportAssets:
						Tags.Add(new ExportAssetsTag(_Reader));
						break;

					case TagType.DoAction:
						Tags.Add(new DoActionTag(_Reader, _CurrentTagLength));
						break;

					case TagType.DoInitAction:
						Tags.Add(new DoActionTag(_Reader, _CurrentTagLength, true));
						break;

					// todo: defineMorphShape
					case TagType.DefineMorphShape:
						Tags.Add(new UnsupportedDefinitionTag(_Reader, "Morphs not supported"));
						_Reader.SkipBytes(_CurrentTagLength); 
						break;
					// todo: defineVideoStream
					case TagType.DefineVideoStream:
						Tags.Add(new UnsupportedDefinitionTag(_Reader, "Video not supported"));
						_Reader.SkipBytes(_CurrentTagLength); 
						break;

                    case TagType.ImportAssets:
                    case TagType.ImportAssets2:
						Tags.Add(new UnsupportedDefinitionTag(_Reader, "Import Assets not yet supported"));
						_Reader.SkipBytes(_CurrentTagLength); 
                        break;
                        // todo: ImportAssets tags

					default:
						// skip if unknown
#if(DEBUG)
						//Debug.WriteLine("unknown type: " + ((uint)_CurrentTag).ToString("X2") + " -- " + Enum.GetName(typeof(TagType), _CurrentTag));
						_Reader.SkipBytes(_CurrentTagLength); 
						break;
                       // throw new Exception("not defined "  + ((uint)curTag).ToString("X2") + " -- " + Enum.GetName(typeof(TagType), curTag));
#else
						Debug.WriteLine("unknown type: " + ((uint)curTag).ToString("X2") + " -- " + Enum.GetName(typeof(TagType), curTag));
						Log.AppendLine("Unhandled swf tag: " + ((uint)curTag).ToString("X2") + " -- " + Enum.GetName(typeof(TagType), curTag));
						r.SkipBytes(curTagLen); 
						break;
#endif
				}
				if (tagEnd != _Reader.Position)
				{
					//Debug.WriteLine("bad tag: " + Enum.GetName(typeof(TagType), _CurrentTag));
					Log.AppendLine("Tag not fully parsed: " + Enum.GetName(typeof(TagType), _CurrentTag)); 
						
					_Reader.Position = tagEnd;
				}
			}
		}
	}
}
